from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QFileDialog, QWidget, \
    QVBoxLayout, QPushButton, QGraphicsEllipseItem, QMessageBox
from PySide6.QtGui import QPixmap, QImage, QWheelEvent, QMouseEvent, QPen, QPolygonF, QBrush, QColor
from PySide6.QtUiTools import QUiLoader
from PySide6.QtCore import Qt, QPointF, QRectF
import cv2 as cv
import numpy as np
import sys
from matplotlib import pyplot as plt
from scipy.fft import fft2, ifft2, fftshift, ifftshift
filename_global = None
#image_global = None
def manual_grayscale(image):
    height, width, channels = image.shape  # cv2格式的图片，高，宽和通道数
    # 创建一个空的灰度图像（仅有一个通道）
    gray_image = np.zeros((height, width), dtype=np.uint8)
    for i in range(height):
        for j in range(width):
            b, g, r = image[i, j]
            gray_value = int(0.299 * r + 0.587 * g + 0.114 * b)
            # gray_value = int((r +g + b)/3)
            gray_image[i, j] = gray_value
    return gray_image
def manual_invert_image(image):
    # 使用 numpy 进行反色处理
    invert_image = np.subtract(255, image)
    return invert_image
def manual_rotate_image(image, angle):
    # 获取图像的高度和宽度
    height, width, channels = image.shape
    # 计算图像的中心
    center_x, center_y = width // 2, height // 2
    # 计算旋转矩阵
    angle_rad = np.deg2rad(angle)
    cos_angle = np.cos(angle_rad)
    sin_angle = np.sin(angle_rad)
    # 计算旋转后的新图像的大小
    new_width = int(abs(width * cos_angle) + abs(height * sin_angle))
    new_height = int(abs(height * cos_angle) + abs(width * sin_angle))
    # 创建一个空的图像，用来存放旋转后的结果
    rotated_image = np.zeros((new_height, new_width, channels), dtype=image.dtype)
    # 计算旋转矩阵的平移部分
    shift_x = (new_width - width) // 2
    shift_y = (new_height - height) // 2
    # 对原图像中的每个像素进行旋转映射
    for i in range(new_height):
        for j in range(new_width):
            # 计算该像素在原图像中的位置
            x = (j - shift_x - center_x) * cos_angle - (i - shift_y - center_y) * sin_angle + center_x
            y = (j - shift_x - center_x) * sin_angle + (i - shift_y - center_y) * cos_angle + center_y

            # 确保映射的坐标在原图像的范围内
            if 0 <= x < width and 0 <= y < height:
                x, y = int(x), int(y)
                rotated_image[i, j] = image[y, x]
    return rotated_image
def manual_nearest_scale(image, new_width, new_height):
    old_height, old_width, channels = image.shape
    # 计算缩放比例
    row_ratio, col_ratio = old_height / new_height, old_width / new_width
    # 创建新的图像数组
    resampled_image = np.zeros((new_height, new_width, channels), dtype=image.dtype)

    for i in range(new_height):
        for j in range(new_width):
            # 计算最邻近的原图像坐标
            orig_x = int(i * row_ratio)
            orig_y = int(j * col_ratio)
            # 赋值
            resampled_image[i, j] = image[orig_x, orig_y]
    return resampled_image
def manual_bilinear_resample(image, new_width, new_height):
    old_height, old_width, channels = image.shape
    row_ratio, col_ratio = old_height / new_height, old_width / new_width
    resampled_image = np.zeros((new_height, new_width, channels), dtype=image.dtype)
    for i in range(new_height):
        for j in range(new_width):
            # 原图中的坐标
            orig_x = i * row_ratio
            orig_y = j * col_ratio
            # 左上角坐标
            x0 = int(np.floor(orig_x))
            y0 = int(np.floor(orig_y))
            # 右下角坐标
            x1 = min(x0 + 1, old_height - 1)
            y1 = min(y0 + 1, old_width - 1)
            # 计算权重
            x_weight = orig_x - x0
            y_weight = orig_y - y0
            # 四个邻近像素的加权平均
            for c in range(channels):
                top = (1 - x_weight) * image[x0, y0, c] + x_weight * image[x1, y0, c]
                bottom = (1 - x_weight) * image[x0, y1, c] + x_weight * image[x1, y1, c]
                resampled_image[i, j, c] = int((1 - y_weight) * top + y_weight * bottom)
    return resampled_image

def shear(image, theta):
    if image is not None:
        theta = np.deg2rad(-theta)  # 将角度转换为弧度
        shear_factor = np.tan(theta)
        h, w = image.shape[:2]
        new_w = int(w + h * abs(shear_factor))  # 计算新的宽度
        sheared_image = np.ones((h, new_w, 3), dtype=np.uint8) * 243
        for y in range(h):
            for x in range(w):
                if shear_factor > 0:
                    new_x = int(x + y * shear_factor)
                else:
                    new_x = int((x - (h-y) * shear_factor))
                # 确保new_x在有效范围内
                if 0 <= new_x < new_w:
                    sheared_image[y, new_x] = image[y, x]
        return sheared_image
    return 0
def apply_homography(image, H, output_shape):
    h, w = output_shape
    h = int(h)
    w = int(w)
    transformed_image = np.zeros((h, w, 3), dtype=np.uint8)
    for y in range(h):
        for x in range(w):
            # 将目标图像的坐标 (x, y) 转换为齐次坐标
            dst_point = np.array([x, y, 1])
            # 逆变换
            src_point = np.dot(np.linalg.inv(H), dst_point)
            src_point = src_point / src_point[2]  # 归一化

            src_x, src_y = int(src_point[0]), int(src_point[1])

            # 检查是否在源图像的边界内
            if 0 <= src_x < image.shape[1] and 0 <= src_y < image.shape[0]:
                transformed_image[y, x] = image[src_y, src_x]
    return transformed_image
def compute_homography(src_points, dst_points):
    #计算透视变换矩阵
    A = []
    for i in range(4):
        x, y = src_points[i]
        u, v = dst_points[i]
        A.append([x, y, 1, 0, 0, 0, -u * x, -u * y, -u])
        A.append([0, 0, 0, x, y, 1, -v * x, -v * y, -v])

    A = np.array(A)
    _, _, V = np.linalg.svd(A)
    H = V[-1].reshape(3, 3)
    H = H / H[2, 2]  # 归一化
    return H
def calculate_histogram(image):
    #计算直方图
    hist = np.zeros(256, dtype=np.int32)
    for pixel in image.flat:
        hist[pixel] += 1
    return hist
def calculate_cdf(hist):# 计算累积分布函数 (CDF)
    cdf = hist.cumsum()
    cdf_normalized = (cdf - cdf.min()) * 255 / (cdf.max() - cdf.min())
    return cdf_normalized.astype(np.uint8)
def apply_histogram_equalization(image, cdf):
    #应用直方图均衡化
    equalized_image = np.interp(image.flatten(), range(256), cdf).reshape(image.shape)
    return equalized_image.astype(np.uint8)

def add_salt_and_pepper_noise(image, salt_prob, pepper_prob):#添加椒盐噪声
    # 获取图像的形状
    rows, cols, channels = image.shape
    # 创建一个与输入图像相同形状的噪声图像
    noisy_image = np.copy(image)
    # 生成随机数，决定哪些像素点添加盐噪声
    num_salt = np.ceil(salt_prob * image.size / channels).astype(int)
    coords = [np.random.randint(0, i - 1, num_salt) for i in [rows, cols]]
    noisy_image[coords[0], coords[1], :] = 255  # 白色 (盐)
    # 生成随机数，决定哪些像素点添加胡椒噪声
    num_pepper = np.ceil(pepper_prob * image.size / channels).astype(int)
    coords = [np.random.randint(0, i - 1, num_pepper) for i in [rows, cols]]
    noisy_image[coords[0], coords[1], :] = 0  # 黑色 (胡椒)
    return noisy_image
def apply_median_filter_to_color_image(image, kernel_size):
    # 分离颜色通道
    red_channel = image[:, :, 0]
    green_channel = image[:, :, 1]
    blue_channel = image[:, :, 2]

    # 对每个通道应用中值滤波
    filtered_red = manual_median_filter(red_channel, kernel_size)
    filtered_green = manual_median_filter(green_channel, kernel_size)
    filtered_blue = manual_median_filter(blue_channel, kernel_size)

    # 重新组合成彩色图像
    filtered_image = np.stack([filtered_red, filtered_green, filtered_blue], axis=-1)

    return filtered_image
def manual_median_filter(image, kernel_size):
    #手动实现中值滤波。
    if kernel_size % 2 == 0:
        raise ValueError("kernel_size must be an odd number")
    pad_width = kernel_size // 2
    padded_image = np.pad(image, pad_width, mode='reflect')  # 边缘填充
    output = np.zeros_like(image)

    for i in range(pad_width, padded_image.shape[0] - pad_width):
        for j in range(pad_width, padded_image.shape[1] - pad_width):
            window = padded_image[i - pad_width:i + pad_width + 1, j - pad_width:j + pad_width + 1]
            output[i - pad_width, j - pad_width] = np.median(window)
    return output
def log(c, img):    # 对数变换
    output = c * np.log(1.0 + img)
    output = np.uint8(output + 0.5)
    return output
def gamma(img, c, v):
    lut = np.zeros(256, dtype=np.float32)
    for i in range(256):
        lut[i] = c * i ** v
    output_img = cv.LUT(img, lut)  # 像素灰度值的映射
    output_img = np.uint8(output_img + 0.5)
    return output_img
def laplacian_sharpen(gray_image):
    img_Laplace = cv.Laplacian(gray_image, -1, ksize=3)
    return img_Laplace
def sobel_sharpen(gray_image):
    # 应用Sobel算子
    sobel_x = cv.Sobel(gray_image, cv.CV_64F, 1, 0, ksize=3)
    sobel_y = cv.Sobel(gray_image, cv.CV_64F, 0, 1, ksize=3)

    # 计算梯度幅值
    sobel = np.sqrt(sobel_x ** 2 + sobel_y ** 2)
    sobel = np.uint8(sobel)
    return sobel
def robert_sharpen(grayImage):
    # Roberts算子
    kernelx = np.array([[-1, 0], [0, 1]], dtype=int)
    kernely = np.array([[0, -1], [1, 0]], dtype=int)
    x = cv.filter2D(grayImage, cv.CV_16S, kernelx)
    y = cv.filter2D(grayImage, cv.CV_16S, kernely)
    # 转uint8
    absX = cv.convertScaleAbs(x)
    absY = cv.convertScaleAbs(y)
    Roberts = cv.addWeighted(absX, 0.5, absY, 0.5, 0)
    return Roberts
def gaussian_lowpass_filter(image, size):
    ksize = (size, size) #(0-30)
    sigmaX = 0
    sigmaY = 0
    # 应用高斯模糊
    blurred_image = cv.GaussianBlur(image, ksize, sigmaX, sigmaY)
    return blurred_image
def butterworth_lowpass_filter(image, cutoff_frequency):
    # 定义Butterworth低通滤波器
    def butterworth_lowpass_filter(shape, cutoff, order=2):
        rows, cols = shape
        crow, ccol = rows // 2, cols // 2
        x, y = np.ogrid[:rows, :cols]
        distance = np.sqrt((x - crow) ** 2 + (y - ccol) ** 2)
        mask = 1 / (1 + (distance / cutoff) ** (2 * order))
        return mask
    # 图像的形状
    rows, cols, channels = image.shape
    # 截止频率和阶数
    order = 2  # 阶数
    # 初始化滤波后的图像
    filtered_image = np.zeros_like(image)
    # 对每个颜色通道分别应用滤波器
    for channel in range(channels):
        # 获取当前通道的图像
        channel_image = image[:, :, channel]
        # 生成Butterworth低通滤波器
        mask = butterworth_lowpass_filter((rows, cols), cutoff_frequency, order)
        # 对图像进行傅里叶变换
        f = fft2(channel_image)
        fshift = fftshift(f)
        # 应用滤波器
        fshift_filtered = fshift * mask
        # 进行逆傅里叶变换
        f_ishift = ifftshift(fshift_filtered)
        img_back = ifft2(f_ishift)
        img_back = np.abs(img_back)

        # 将滤波后的图像赋值给对应的颜色通道
        filtered_image[:, :, channel] = img_back

    img_return = filtered_image.astype(np.uint8)
    return img_return
def ideal_lowpass_filter(image, cutoff_frequency):
    # 将图像转换为float32
    image = np.float32(image)
    # 获取图像尺寸
    rows, cols, channels = image.shape
    # 对每个颜色通道进行傅里叶变换
    f_transforms = [np.fft.fftshift(np.fft.fft2(channel)) for channel in cv.split(image)]
    # 创建理想低通滤波器掩码
    crow, ccol = rows // 2, cols // 2
    mask = np.zeros((rows, cols), np.uint8)
    cv.circle(mask, (ccol, crow), cutoff_frequency, 1, -1)
    # 应用滤波器
    filtered_f_transforms = [f * mask for f in f_transforms]
    # 逆傅里叶变换
    filtered_images = [np.abs(np.fft.ifft2(np.fft.ifftshift(f))) for f in filtered_f_transforms]
    # 合并颜色通道
    result_image = cv.merge(filtered_images)
    # 转换回uint8
    result_image = np.uint8(result_image)
    return result_image
def high_pass_filter(img, value):
    # 分离颜色通道
    b, g, r = cv.split(img)
    # 对每个颜色通道进行傅里叶变换
    f_b = np.fft.fft2(b)
    f_g = np.fft.fft2(g)
    f_r = np.fft.fft2(r)

    # 将频谱低频从左上角移动至中心位置
    fshift_b = np.fft.fftshift(f_b)
    fshift_g = np.fft.fftshift(f_g)
    fshift_r = np.fft.fftshift(f_r)

    # 设置高通滤波器
    rows, cols, _ = img.shape
    crow, ccol = int(rows / 2), int(cols / 2)

    # 创建高通滤波器掩码
    mask = np.ones((rows, cols), np.uint8)
    mask[crow-value:crow+value, ccol-value:ccol+value] = 0

    # 应用高通滤波器
    fshift_b *= mask
    fshift_g *= mask
    fshift_r *= mask

    # 进行傅里叶逆变换
    ishift_b = np.fft.ifftshift(fshift_b)
    ishift_g = np.fft.ifftshift(fshift_g)
    ishift_r = np.fft.ifftshift(fshift_r)

    iimg_b = np.fft.ifft2(ishift_b)
    iimg_g = np.fft.ifft2(ishift_g)
    iimg_r = np.fft.ifft2(ishift_r)

    iimg_b = np.abs(iimg_b)
    iimg_g = np.abs(iimg_g)
    iimg_r = np.abs(iimg_r)

    # 合并颜色通道
    filtered_img = cv.merge((iimg_b, iimg_g, iimg_r))
    return filtered_img.astype(np.uint8)

class CustomGraphicsView(QGraphicsView): #自定义画板
    def __init__(self, parent=None):
        super(CustomGraphicsView, self).__init__(parent)
        self.scene = QGraphicsScene(self)
        self.setScene(self.scene)
        self.pixmap_item = None #存储图片
        self.scale_factor = 1.0 #缩放因子
        self.is_dragging = False#是否正在拖拽
        self.drag_start_pos = None#存储拖拽开始的位置
        self.last_mouse_position = QPointF()#存储鼠标的最后一个位置

    def set_image(self, image_path): #加载图片
        # 加载图片并设置到场景中
        pixmap = QPixmap(image_path)
        self.pixmap_item = QGraphicsPixmapItem(pixmap)
        self.scene.addItem(self.pixmap_item)
        self.scene.setSceneRect(pixmap.rect())
        self.fit_in_view()  # 初始时适应视图大小

    def fit_in_view(self):
        # 使图片适应视图大小，保持宽高比
        rect = self.scene.sceneRect()
        if not rect.isNull():
            self.setSceneRect(rect)
            unity = self.transform().mapRect(QRectF(0, 0, 1, 1))
            self.scale(1 / unity.width(), 1 / unity.height())
            viewrect = self.viewport().rect()
            xratio = viewrect.width() / rect.width()
            yratio = viewrect.height() / rect.height()
            self.scale(min(xratio, yratio), min(xratio, yratio))

    def wheelEvent(self, event: QWheelEvent):
        # 处理滚轮事件以实现缩放
        zoom_in_factor = 1.25
        zoom_out_factor = 1 / zoom_in_factor

        # 获取滚轮的方向
        if event.angleDelta().y() > 0:
            zoom_factor = zoom_in_factor
        else:
            zoom_factor = zoom_out_factor

        # 计算新的缩放因子，但不超过一定的限制
        self.scale_factor *= zoom_factor
        self.scale_factor = min(max(0.1, self.scale_factor), 10.0)  # 限制缩放范围在0.1到10之间

        # 应用缩放
        self.scale(zoom_factor, zoom_factor)

    def mousePressEvent(self, event: QMouseEvent):
        super().mousePressEvent(event)
        # 处理鼠标按下事件以开始拖动
        if event.button() == Qt.LeftButton:
            print("检测到点击")
            self.drag_start_pos = event.position().toPoint()  # 使用 position() 并转换为 QPoint
            self.is_dragging = True

    def mouseMoveEvent(self, event):
        if self.is_dragging:
            print("检测到drag")
            delta = event.position().toPoint() - self.drag_start_pos
            self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() - delta.x())
            self.verticalScrollBar().setValue(self.verticalScrollBar().value() - delta.y())
            self.drag_start_pos = event.position().toPoint()  # 更新拖动起始位置
    def mouseReleaseEvent(self, event):
        # 处理鼠标释放事件以停止拖动
        if event.button() == Qt.LeftButton:
            print("检测到释放")
            self.is_dragging = False
class ClickablePixmapItem(QGraphicsPixmapItem):
    def __init__(self,parent, *args, **kwargs):
        self.parent = parent
        super(ClickablePixmapItem, self).__init__(*args, **kwargs)
        self.setFlags(QGraphicsPixmapItem.ItemIsSelectable | QGraphicsPixmapItem.ItemIsFocusable)
        self.tipList =["右上角","右下角","左下角",]

    def mousePressEvent(self, event):
        #print("Pixmap鼠标点击")  # 或者任何其他处理逻辑
        super().mousePressEvent(event)
        if event.button() == Qt.LeftButton and self.parent.is_toushi_mode:
            pos = self.mapToScene(event.pos())  # 更新为 position().toPoint()
            for i, point in enumerate(self.parent.points):
                if (pos - point).manhattanLength() < 10:  # 点击范围
                    self.parent.is_dragging = True
                    self.parent.dragged_point = i
                    break
            else:
                if len(self.parent.points) < 4:
                    self.parent.points.append(pos)
                    self.parent.add_point_item(pos)
                    print(f"已点击第 {len(self.parent.points)} 个点，请继续点击其他点以形成四边形。")
                    print(f"当前点击的点坐标：{pos.x()}, {pos.y()}")
                    if len(self.parent.points)<=3:
                        self.parent.ui.label_toushi.setText(f"请在图像上确认四边形{self.tipList[len(self.parent.points)-1]}")
                    else:
                        self.parent.ui.label_toushi.setText("拖动顶点调整形状,点击确认")
                    self.parent.update_polygon()


    def mouseMoveEvent(self, event):
        super().mouseMoveEvent(event)
        if self.parent.is_dragging and self.parent.dragged_point is not None and self.parent.is_toushi_mode:
            pos = self.mapToScene(event.pos())  # 更新为 position().toPoint()
            self.parent.points[self.parent.dragged_point] = pos
            self.parent.point_items[self.parent.dragged_point].setRect(pos.x() - 5, pos.y() - 5, 10, 10)
            self.parent.update_polygon()
        #print("Pixmap鼠标拖动")

    def mouseReleaseEvent(self, event):
        super().mouseReleaseEvent(event)
        self.parent.is_dragging = False
        self.parent.dragged_point = None
        #print("Pixmap鼠标释放")
class ImageViewer:
    def __init__(self, graphics_view):
        loader = QUiLoader()
        self.ui = loader.load("imageViewer.ui")
        self.ui.setWindowTitle("Image Viewer")

        self.graphics_view = graphics_view
        self.scene = QGraphicsScene(self.graphics_view)
        self.ui.graphicsView.setScene(self.scene)

        self.pixmap_item = ClickablePixmapItem(self)
        #self.pixmap_item = QGraphicsPixmapItem()
        self.scene.addItem(self.pixmap_item)

        self.image_original = None  # 原始图片
        self.image_current = None  # 当前操作的图片
        self.image_scale = None #用于缩放的图片
        self.image_noise = None #用于处理噪声的图片
        self.is_gray = False  # 标志是否为灰度化状态
        self.is_preview = True  # 标志是否为预览状态
        self.highpass_filter_mode = False #标志是否为高通滤波状态

        self.selected_index_2 = 0  # 存储下拉框2的选中索引
        self.selected_index_3 = 0   # 存储下拉框3的选中索引
        self.selected_index_4 = 0   # 存储下拉框4的选中索引

        # 用于存储四边形顶点的列表
        self.point_items = []  # 存储顶点项
        self.is_dragging = False
        self.dragged_point = None
        self.polygon_item = None
        self.is_toushi_mode = False
        self.is_median_filter = False


        # 启用手型拖拽
        self.ui.graphicsView.setDragMode(QGraphicsView.ScrollHandDrag)

        # 隐藏滚动条
        # self.ui.graphicsView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        # self.ui.graphicsView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        # 按钮绑定功能
        self.ui.button_open.clicked.connect(self.open_image)
        self.ui.button_rotate1.clicked.connect(lambda: self.rotate_image(-90))
        self.ui.button_rotate2.clicked.connect(lambda: self.rotate_image(90))
        self.ui.graphicsView.setSceneRect(self.pixmap_item.boundingRect())
        self.ui.button_newwin.clicked.connect(self.showNewWindow)
        self.ui.comboBox_1.currentIndexChanged.connect(self.on_combobox1_changed)

        self.ui.comboBox_2.currentIndexChanged.connect(self.on_combobox2_changed)
        #点击多项选择框,改变索引，0则将slider1归零，1则归零，2则归1，并设置label1的值
        self.ui.comboBox_3.currentIndexChanged.connect(self.on_combobox3_changed)
        #选择锐化的类型，0为无，1为Roberts算子，2为Sobel算子，3为Laplace算子
        self.ui.comboBox_4.currentIndexChanged.connect(self.on_combobox4_changed)
        #选择滤波器类型，0为原图，1为理想低通滤波，2为Butterworth滤波，3为高斯滤波。0，1,2,3都将slider1归1并设置label1的值分别为0,1,1,1
        self.ui.slider1.valueChanged.connect(self.on_slider_value_changed)
        # ↑ 根据索引值，0为不动，拖动后再设置为0；1可动，传入斜切函数；2可动，传入缩放函数。
        self.ui.slider_lowpass.valueChanged.connect(self.on_slider_lowpass_changed)
        self.ui.slider_highpass.valueChanged.connect(self.on_slider_highpass_changed)

        self.ui.button_return.clicked.connect(self.slider_return)
        # ↑ 根据索引值，0，1设置为0；2设置缩放比为1.
        self.ui.button_toushi.clicked.connect(self.start_toushi_mode)#点击按钮，进入透视变换模式
        self.ui.button_yes.clicked.connect(self.perform_perspective_transform)#点击按钮，执行透视变换
        self.ui.button_reset.clicked.connect(self.reset_image)#点击按钮，重置图片
        self.ui.button_add_noise.clicked.connect(self.add_noise)#点击按钮，添加噪声
        self.ui.button_median_filter.clicked.connect(self.is_median_filter_mode)#点击按钮，中值滤波
        self.ui.slider_kernel.valueChanged.connect(self.on_slider_kernel_changed)
        self.ui.button_save.clicked.connect(self.save_image)
        self.ui.button_hist.clicked.connect(self.show_histogram)
        self.ui.button_log.clicked.connect(self.log_transform)
        self.ui.button_gamma.clicked.connect(self.gamma_transform)
        self.ui.button_sharpen.clicked.connect(self.sharpen_image)
        self.ui.button_fourier.clicked.connect(self.fourier_transform)
        self.ui.button_highpass.clicked.connect(self.highpass_filter)
        self.ui.button_equalize.clicked.connect(self.equalize_histogram)

    def open_image(self):
        file_path, _ = QFileDialog.getOpenFileName(
            None, "Open Image", "", "Image Files (*.png *.jpg *.jpeg *.bmp)"
        )
        if file_path:
            self.image_original = cv.imread(file_path)
            self.image_current = self.image_original.copy()
            self.image_scale = self.image_original.copy()

            global filename_global
            filename_global = file_path
            self.is_gray = False  # 重置灰度状态
            self.display_preview()
            self.display_graphics_view()

    def display_preview(self):
        if self.image_original is not None:
            image_rgb = cv.cvtColor(self.image_original, cv.COLOR_BGR2RGB)
            h, w, ch = image_rgb.shape
            bytes_per_line = ch * w
            q_image = QImage(image_rgb.data, w, h, bytes_per_line, QImage.Format_RGB888)
            pixmap = QPixmap.fromImage(q_image)
            self.ui.label.setPixmap(pixmap.scaled(self.ui.label.size(), Qt.KeepAspectRatio))

    def img_setscene(self, image):
        image_rgb = cv.cvtColor(image, cv.COLOR_BGR2RGB)
        h, w, ch = image_rgb.shape
        bytes_per_line = ch * w
        q_image = QImage(image_rgb.data, w, h, bytes_per_line, QImage.Format_RGB888)
        pixmap = QPixmap.fromImage(q_image)

        # 打印调试信息
        print(f"Image dimensions: {w} x {h}")

        # 更新 QGraphicsPixmapItem 的 pixmap 属性
        self.pixmap_item.setPixmap(pixmap)

        # 更新 QGraphicsView 的场景矩形
        self.scene.setSceneRect(0, 0, w, h)
        # 将视图的矩形设置为指定的场景矩形
        self.graphics_view.setSceneRect(self.scene.sceneRect())

    def display_graphics_view(self):
        if self.image_current is not None:
            self.img_setscene(self.image_current)

            # 确保场景矩形设置为当前pixmap的边界
            self.graphics_view.setSceneRect(self.scene.sceneRect())

            # 使用QGraphicsView的水平和垂直滚动条
            self.graphics_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
            self.graphics_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)

            # 切换到拖拽模式
            self.graphics_view.setDragMode(QGraphicsView.ScrollHandDrag)

    def toggle_gray(self):
        """切换灰度化和恢复原色"""
        if self.image_current is not None:
            if not self.is_gray:
                gray_image = cv.cvtColor(self.image_current, cv.COLOR_BGR2GRAY)
                self.image_current = cv.cvtColor(gray_image, cv.COLOR_GRAY2BGR)
            else:
                self.image_current = self.image_original.copy()
            self.is_gray = not self.is_gray
            self.display_graphics_view()

    def invert_color(self):
        if self.image_current is not None:
            self.image_current = cv.bitwise_not(self.image_current)
            self.display_graphics_view()

    def rotate_image(self, angle):
        if self.image_current is not None:
            center = (self.image_current.shape[1] // 2, self.image_current.shape[0] // 2)
            widthNew = self.image_current.shape[0]
            heightNew = self.image_current.shape[1]
            matrix = cv.getRotationMatrix2D(center, angle, 1.0)
            matrix[0,2]+=(widthNew-self.image_current.shape[1])/2
            matrix[1,2]+=(heightNew-self.image_current.shape[0])/2

            rotated_image = cv.warpAffine(
                self.image_current,
                matrix,
                (widthNew, heightNew,),
                flags=cv.INTER_LINEAR,borderValue=(243, 243, 243)
            )
            self.image_current = rotated_image
            print("旋转后：",self.image_current.shape)
            self.display_graphics_view()

    def on_combobox1_changed(self, index):
        if index == 0:
            self.image_current =self.image_original
            self.is_preview = True
            self.is_gray=False
            self.display_graphics_view()
            print("原图 is selected")
        elif index == 1:
            self.toggle_gray()
            print("灰度化 is selected")
        elif index == 2:
            self.invert_color()
            print("反色 is selected")

    def on_combobox2_changed(self, index):
        if index == 0:
            self.selected_index_2 =0
            self.ui.slider1.setValue(0) #设置到中间
            print("原图 is selected")
            self.ui.label_combo2.setText("当前为原始图像")
            self.image_current =self.image_original#重置当前图像
            self.image_scale = self.image_current
            self.is_gray =False

            self.is_preview = True      #重设标志
            self.display_graphics_view() #展示当前图像

        elif index == 1:
            self.selected_index_2 =1
            self.ui.slider1.setValue(0) #设置到中间
            print("斜切 is selected")

            self.ui.label_combo2.setText("当前斜切角度: 0°")#希望达到（-60,60）

        elif index == 2:
            self.selected_index_2 =2
            self.ui.slider1.setValue(0) #设置到中间
            print("缩放 is selected")

            self.ui.label_combo2.setText("当前缩放比例为: 1")#希望达到（0.2,1.8）

    def on_combobox3_changed(self,index):
        if index == 0:
            self.selected_index_3 =0
            print("无 is selected")
            self.image_current =self.image_original#重置当前图像
            self.image_scale = self.image_current
            self.is_gray =False
            self.is_preview = True      #重设标志
            self.display_graphics_view() #展示当前图像
        elif index == 1:
            self.selected_index_3 =1
            print("Robert算子 is selected")
        elif index == 2:
            self.selected_index_3 =2
            print("Sobel算子 is selected")
        elif index == 3:
            self.selected_index_3 =3
            print("Laplace算子 is selected")

    def on_combobox4_changed(self,index):
        if index == 0:
            self.selected_index_4 =0
            self.ui.slider_lowpass.setValue(1)  # 设置到中间
            self.ui.label_lowpass.setText("请选择低通滤波方式")
            print("原图 is selected")
            self.image_current =self.image_original#重置当前图像
            self.image_scale = self.image_current
            self.is_gray =False
            self.is_preview = True      #重设标志
            self.display_graphics_view() #展示当前图像
        elif index == 1:
            self.selected_index_4 =1
            self.ui.slider_lowpass.setValue(30)  # 设置到中间
            if self.image_current is not None:
                self.ui.label_lowpass.setText("当前半径: 30")
            else:
                self.ui.label_lowpass.setText("当前半径:")
            print("理想低通滤波器 is selected")
        elif index == 2:
            self.selected_index_4 =2
            self.ui.slider_lowpass.setValue(30)  # 设置到中间
            if self.image_current is not None:
                self.ui.label_lowpass.setText("当前半径: 30")
            else:
                self.ui.label_lowpass.setText("当前半径:")
            print("Butterworth低通滤波器 is selected")
        elif index == 3:
            self.selected_index_4 =3
            self.ui.slider_lowpass.setValue(1)  # 设置到中间
            if self.image_current is not None:
                self.ui.label_lowpass.setText(f"卷积核大小: ({1},{1})")
            else:
                self.ui.label_lowpass.setText("卷积核大小:")
            print("Gaussian低通滤波器 is selected")

    def sharpen_image(self):
        if self.image_current is not None:
            if self.selected_index_3 == 0:
                print("当前为原始图像，请选择其他选项")

            elif self.selected_index_3 == 1:
                print("使用Robert算子锐化")
                if not self.is_gray:
                    self.image_current = cv.cvtColor(self.image_current, cv.COLOR_BGR2GRAY)
                    self.is_gray = True
                self.image_current = robert_sharpen(self.image_current)

            elif self.selected_index_3 == 2:
                print("使用Sobel算子锐化")
                if not self.is_gray:
                    self.image_current = cv.cvtColor(self.image_current, cv.COLOR_BGR2GRAY)
                    self.is_gray = True
                self.image_current = sobel_sharpen(self.image_current)

            elif self.selected_index_3 == 3:
                print("使用Laplace算子锐化")
                if not self.is_gray:
                    self.image_current =cv.cvtColor(self.image_current, cv.COLOR_BGR2GRAY)
                    self.is_gray = True
                self.image_current= laplacian_sharpen(self.image_current)

            self.image_scale =self.image_current
            self.display_graphics_view()

    def on_slider_value_changed(self, value):   #滑动时触发
        #print("self.selected_index_2:", self.selected_index_2)

        if self.selected_index_2==0:
            print("当前为原始图像，请选择其他选项")
            self.ui.slider1.setValue(0)

        elif self.selected_index_2==1:

            if self.image_current is not None:
                self.ui.label_combo2.setText(f"当前斜切角度为: {value}°")
                # 调用 shear 函数进行处理
                image_sheared = shear(self.image_current, theta=value)
                img_tem = self.image_current#临时存储图像
                self.image_current = image_sheared
                self.display_graphics_view()

                self.image_current = img_tem  # 恢复图像

            else:
                print("当前没有图像")
                self.ui.slider1.setValue(0)

        elif self.selected_index_2==2: #选中了缩放功能
            if self.image_current is not None:
                ratio = float((value+72)/60)
                self.ui.label_combo2.setText(f"当前缩放比例为: {((value+72)/60):.1f}")
                # 获取宽和高
                if self.is_gray:
                    print("灰度图尺寸：",self.image_scale.shape)
                    h, w = self.image_scale.shape
                else:
                    h, w, _ = self.image_scale.shape
                new_width = int(w * ratio)
                new_height = int(h * ratio)
                #scaled_image_cv = opencv_scale(self.image_scale, new_width, new_height)# 调用缩放函数进行处理
                #self.image_current = scaled_image_cv
                scaled_image_py = manual_bilinear_resample(self.image_scale, new_width, new_height)
                self.image_current = scaled_image_py
                self.display_graphics_view()
            else:
                print("当前没有图像")
                self.ui.slider1.setValue(0)

    def on_slider_lowpass_changed(self, value):   #滑动时触发
        if self.selected_index_4==0:
            print("当前为原始图像，请选择其他选项")
            self.ui.slider_lowpass.setValue(1)

        elif self.selected_index_4==1: #理想低通滤波器

            if self.image_current is not None:
                self.image_current =self.image_scale
                self.ui.label_lowpass.setText(f"当前半径: {value*2}")
                self.image_current = ideal_lowpass_filter(self.image_current, value*2)
                self.display_graphics_view()
            else:
                print("当前没有图像")
                self.ui.slider_lowpass.setValue(30)

        elif self.selected_index_4==2:#Butterworth低通滤波器
            if self.image_current is not None:
                self.image_current = self.image_scale
                self.ui.label_lowpass.setText(f"当前半径: {value*2}")
                image_temp = self.image_current
                self.image_current = butterworth_lowpass_filter(self.image_current, value*2)
                self.display_graphics_view()
                self.image_current = image_temp#展示完以后再恢复图像

            else:
                print("当前没有图像")
                self.ui.slider_lowpass.setValue(30)
        elif self.selected_index_4==3:#高斯低通滤波器
            if self.image_current is not None:
                self.image_current = self.image_scale
                self.ui.label_lowpass.setText(f"卷积核大小: ({value},{value})")
                image_temp = self.image_current
                self.image_current = gaussian_lowpass_filter(self.image_current, 2*value-1)#卷积核要为奇数
                self.display_graphics_view()
                self.image_current = image_temp#展示完以后再恢复图像

            else:
                print("当前没有图像")
                self.ui.label_lowpass.setText("卷积核大小:")
                self.ui.slider_lowpass.setValue(1)

    def slider_return(self):
        print("你点击了复原")
        self.ui.slider1.setValue(0)

        if self.selected_index_2==0:
            self.ui.label_combo2.setText("当前为原始图像")

        elif self.selected_index_2 == 1:
            self.ui.label_combo2.setText("当前斜切角度为: 0°")

        elif self.selected_index_2==2:
            self.ui.label_combo2.setText("当前缩放比例为:1")

    def perform_perspective_transform(self):
        if not self.is_toushi_mode or len(self.points) != 4:
            print("请先进入透视变换模式并点击四个点。")
            self.parent.ui.label_toushi.setText("请先进入透视变换模式并点击四个点。")
            return
        # 获取原始图片的OpenCV格式数据
        img = self.image_current  # 替换为实际图片路径
        # 将Qt的点坐标转换为OpenCV中的坐标格式
        cv_points = np.array([[point.x(), point.y()] for point in self.points], dtype=np.float32)
        print("cv_points:",cv_points)
        # 定义目标四边形的坐标
        width = max(np.sqrt(((cv_points[0][0] - cv_points[1][0]) ** 2) + ((cv_points[0][1] - cv_points[1][1]) ** 2)),
                    np.sqrt(((cv_points[2][0] - cv_points[3][0]) ** 2) + ((cv_points[2][1] - cv_points[3][1]) ** 2)))
        height = max(np.sqrt(((cv_points[0][0] - cv_points[3][0]) ** 2) + ((cv_points[0][1] - cv_points[3][1]) ** 2)),
                     np.sqrt(((cv_points[1][0] - cv_points[2][0]) ** 2) + ((cv_points[1][1] - cv_points[2][1]) ** 2)))
        target_points = np.array([[0, 0], [width - 1, 0], [width - 1, height - 1], [0, height - 1]], dtype="float32")
        print("target_points:",target_points)
        # 计算透视变换矩阵
        H = compute_homography(cv_points, target_points)
        # 应用透视变换
        transformed_image = apply_homography(img, H, (target_points[2][1], target_points[2][0]))

        cv.imshow("transformed_image ", transformed_image)


        self.image_scale = transformed_image #用于保存选中的区域，后面进行缩放
        self.image_current = transformed_image #用于显示

        # 移除当前的图像项
        self.scene.removeItem(self.pixmap_item)
        self.pixmap_item = ClickablePixmapItem(self)
        self.scene.addItem(self.pixmap_item)

        # 将透视变换后的图像添加到场景中
        self.display_graphics_view()
        # 移除之前的点和多边形
        self.points=[] #把四边形顶点的坐标置空
        self.remove_point_items()
        self.update_polygon()  # 可以选择在这里不绘制新的多边形

        self.is_toushi_mode = False
        print("透视变换完成。")

    def start_toushi_mode(self):
        self.is_toushi_mode = True
        self.points = []
        self.remove_point_items()
        self.update_polygon()
        self.ui.graphicsView.setDragMode(QGraphicsView.NoDrag)
        self.ui.label_toushi.setText("请在图像上确认四边形左上角")

    def remove_point_items(self):
        for item in self.point_items.copy():  # 使用副本避免迭代过程中修改列表
            try:
                if item.scene() is not None:  # 检查item是否还在场景中
                    self.scene.removeItem(item)
            except RuntimeError:
                continue  # 如果对象已经被删除，跳过
        self.point_items.clear()

    def update_polygon(self):
        if self.polygon_item:
            self.scene.removeItem(self.polygon_item)
            self.polygon_item = None  # 确保多边形项被正确重置
        if len(self.points) == 4:
            polygon = QPolygonF(self.points)
            self.polygon_item = self.scene.addPolygon(polygon, QPen(Qt.blue, 2))

    def add_point_item(self, point):
        item = QGraphicsEllipseItem(point.x() - 5, point.y() - 5, 10, 10)  # 顶点大小为10x10
        item.setBrush(QBrush(QColor(255, 0, 0)))  # 顶点颜色为红色
        item.setPen(QPen(Qt.black))
        self.scene.addItem(item)
        self.point_items.append(item)

    def reset_image(self):
        if self.image_original is not None:
            self.image_current = self.image_original.copy()
            self.image_scale =self.image_current
            self.display_graphics_view()
            self.is_gray=False
            #重置文本和滑动条
            self.ui.label_toushi.setText("请在图像上确认四边形左上角")
            self.ui.slider_kernel.setValue(1)
            self.ui.label_kernel.setText("核大小: ")

    def add_noise(self):
        salt_prob = 0.01  # 盐噪声的概率
        pepper_prob = 0.01  # 胡椒噪声的概率
        noisy_image = add_salt_and_pepper_noise(self.image_current, salt_prob, pepper_prob)
        self.image_current = noisy_image
        self.image_scale =   noisy_image
        self.image_noise =   noisy_image
        self.display_graphics_view()


    def is_median_filter_mode(self):
        if self.image_noise is not None:
            self.median_filter(1)
            self.ui.label_kernel.setText("核大小:1")
            self.is_median_filter = True
        else:
            print("请先添加噪声")
            self.ui.slider_kernel.setValue(1)

    def on_slider_kernel_changed(self,value):
        print("当前slider数值为：",value)
        if self.is_median_filter:
            if self.image_noise is not None:
                value = value*2-1  #核大小 1-9，且为奇数
                self.median_filter(value)
                self.ui.label_kernel.setText(f"核大小:{value}")
            else:
                print("请先添加噪声")
                self.ui.slider_kernel.setValue(1)
        else:
            print("请先进入中值滤波模式")
            self.ui.slider_kernel.setValue(1)

    def median_filter(self,kernel_size):
        #应用中值滤波
        if self.image_noise is not None:
            #kernel_size  滤波器的大小
            filtered_image = apply_median_filter_to_color_image(self.image_noise, kernel_size)
            self.image_current = filtered_image
            self.image_scale = filtered_image
            self.display_graphics_view()

    def save_image(self):
        if self.image_current is not None:
            # 选择文件夹
            folder_name = QFileDialog.getExistingDirectory(self, "选择文件夹", "../")
            # 选择文件
            file_name, _ = QFileDialog.getOpenFileNames(self, 'Open Images', '../', 'Image files (*.png *.jpg)')

    def show_histogram(self):
        if self.image_current is not None:
            src = self.image_current

            histb = cv.calcHist([src], [0], None, [256], [0, 255])
            if not self.is_gray:
                histg = cv.calcHist([src], [1], None, [256], [0, 255])
                histr = cv.calcHist([src], [2], None, [256], [0, 255])
                plt.plot(histg, color='g')
                plt.plot(histr, color='r')
            plt.plot(histb, color='b')
            plt.title("Histogram")
            plt.show()

    def equalize_histogram(self):
        if self.image_current is not None:
            if not self.is_gray:
                self.image_current = cv.cvtColor(self.image_current, cv.COLOR_BGR2GRAY)
                self.is_gray =True
            hist = calculate_histogram(self.image_current)
            # 计算累积分布函数 (CDF)
            cdf = calculate_cdf(hist)
            # 应用直方图均衡化
            equalized_image = apply_histogram_equalization(self.image_current, cdf)
            self.image_current = equalized_image
            self.image_scale = equalized_image
            self.display_graphics_view()

    def log_transform(self):
        if self.image_current is not None:
            output = log(42, self.image_current)
            self.image_current = output
            self.image_scale = output
            self.display_graphics_view()

    def gamma_transform(self):
        if self.image_current is not None:
            output = gamma(self.image_current, 0.00000005, 4.0)
            self.image_current = output
            self.image_scale = output
            self.display_graphics_view()

    def fourier_transform(self):
        if self.image_current is not None:
            if not self.is_gray:
                self.image_current = cv.cvtColor(self.image_current, cv.COLOR_BGR2GRAY)
            # 傅里叶变换
            dft = cv.dft(np.float32(self.image_current), flags=cv.DFT_COMPLEX_OUTPUT)
            # 将频谱低频从左上角移动至中心位置
            dft_shift = np.fft.fftshift(dft)
            # 计算频谱的幅度
            magnitude_spectrum = 20 * np.log(cv.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1]))
            # 归一化频谱图像，确保像素值在0到255之间
            magnitude_spectrum = cv.normalize(magnitude_spectrum, None, 0, 255, cv.NORM_MINMAX, dtype=cv.CV_8U)
            cv.namedWindow("Spectrum", cv.WINDOW_NORMAL)
            cv.imshow("Spectrum", magnitude_spectrum)
            # self.image_current = magnitude_spectrum
            # #self.image_scale = magnitude_spectrum
            # self.display_graphics_view()

    def highpass_filter(self):
        self.ui.slider_highpass.setValue(1)
        if self.image_current is not None:
            self.ui.label_highpass.setText("滤波窗口:[1,1]")
            self.highpass_filter_mode = True
        else:
            print("请先打开图像")
            self.ui.label_highpass.setText("滤波窗口:")
    def on_slider_highpass_changed(self,value):
        if self.image_current is not None:
            self.image_current = self.image_scale
            if self.highpass_filter_mode:
                self.ui.label_highpass.setText("滤波窗口:[{},{}]".format(value,value))
                self.image_current = high_pass_filter(self.image_current, value)
                self.display_graphics_view()
            else:
                self.ui.label_highpass.setText("滤波窗口:")
                self.ui.slider_highpass.setValue(1)
        else:
            print("请先打开图像")
            self.ui.label_highpass.setText("滤波窗口:")
            self.ui.slider_highpass.setValue(1)


    def showNewWindow(self):
        #global image_global # 将当前图像赋值给全局变量
        newwin.show()
class NewWindow(QWidget):
    def __init__(self):
        super(NewWindow, self).__init__()
        layout = QVBoxLayout(self)
        self.setWindowTitle('图像拖动与缩放')
        self.resize(800, 640)

        # 创建 QGraphicsView 实例
        self.graphics_view = CustomGraphicsView(self)
        self.image_viewer = CustomGraphicsView(self.graphics_view)


        self.button_open = QPushButton("传递图像", self)
        self.button_open.clicked.connect(self.openImage)
        layout.addWidget(self.button_open)
        layout.addWidget(self.image_viewer)  # 添加 ImageViewer 的 UI 到布局中
        self.setLayout(layout)

    def openImage(self):
        global filename_global
        if filename_global is not None:
            self.image_viewer.set_image(filename_global)
        else:
            print("请先打开图像")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    viewer = ImageViewer(CustomGraphicsView())  # 传递一个 CustomGraphicsView 实例
    newwin = NewWindow()
    viewer.ui.show()
    sys.exit(app.exec())






